#include "qae_qansiline.h"
#include "wcwidth.h"
#include <QtCore/QTextCodec>
#include <QtCore/QDebug>


namespace QAnsiEditor {


QAnsiLine::QAnsiLine()
{
    reset();
    updateIndex();
}

QAnsiLine::QAnsiLine(const QString &strText)
{
    reset();
    m_strContent = strText;
    updateIndex();
    m_arrColors.fill(m_chCurrentColor, getLength());
    m_arrEffects.fill(m_chCurrentEffect, getLength());
}

QAnsiLine::~QAnsiLine()
{

}

int QAnsiLine::getLength() const
{
    return m_arrIndex.length();
}

int QAnsiLine::getTextLength() const
{
    return m_strContent.length();
}

char QAnsiLine::getColor(int iIndex) const
{
    if (iIndex < 0 || iIndex > m_arrColors.size()) {
        return -1;
    }
    return m_arrColors[iIndex];
}

void QAnsiLine::setColor(int iIndex, char chColor)
{
    if (iIndex < 0 || iIndex > m_arrColors.size()) {
        return;
    }
    m_arrColors[iIndex] = chColor;
}

char QAnsiLine::getCurrentColor() const
{
    return m_chCurrentColor;
}

void QAnsiLine::setCurrentColor(char chColor)
{
    m_chCurrentColor = chColor;
}

char QAnsiLine::getEffect(int iIndex) const
{
    if (iIndex < 0 || iIndex > m_arrEffects.size()) {
        return -1;
    }
    return m_arrEffects[iIndex];
}

void QAnsiLine::setEffect(int iIndex, char chAttribute)
{
    if (iIndex < 0 || iIndex > m_arrEffects.size()) {
        return;
    }
    m_arrEffects[iIndex] = chAttribute;
}

char QAnsiLine::getCurrentEffect() const
{
    return m_chCurrentEffect;
}

void QAnsiLine::setCurrentEffect(char chAttribute)
{
    m_chCurrentEffect = chAttribute;
}

void QAnsiLine::insertText(const QString &strText, int iIndex)
{
    if (strText.isEmpty()) {
        return;
    }

    int iChangeStart = 0;
    int iOldLen = getLength();
    int iLen = QAnsiLine(strText).getLength();
    QByteArray temp;

    if (iIndex <= -1) {  // append
        iChangeStart = getLength();
        // content
        m_strContent.append(strText);
        // color
        temp.fill(m_chCurrentColor, iLen);
        m_arrColors.append(temp);
        // effect
        temp.fill(m_chCurrentEffect, iLen);
        m_arrEffects.append(temp);
    } else {  // insert
        if (iIndex >= getLength()) {
            iChangeStart = getLength();
            // content
            m_strContent += QString(iIndex-getLength(), ' ');
            m_strContent.append(strText);
            // color
            temp.fill(m_chCurrentColor, iIndex-getLength()+iLen);
            m_arrColors.append(temp);
            // effect
            temp.fill(m_chCurrentEffect, iIndex-getLength()+iLen);
            m_arrEffects.append(temp);
        } else {
            if (iIndex == 0) {
                iChangeStart = 0;
            } else {  // > 0
                if (isPartial(iIndex)) {  // in middle of a character
                    iChangeStart = iIndex - 1;  // insert before the whole character
                } else {
                    iChangeStart = iIndex;
                }
            }
            // content
            m_strContent.insert(m_arrIndex[iChangeStart], strText);
            // color
            temp.fill(m_chCurrentColor, iLen);
            m_arrColors.insert(iChangeStart, temp);
            // effect
            temp.fill(m_chCurrentEffect, iLen);
            m_arrEffects.insert(iChangeStart, temp);
        }
    }

    updateIndex();
    Q_ASSERT(m_arrIndex.size() == m_arrColors.size());
    Q_ASSERT(m_arrIndex.size() == m_arrEffects.size());
    setChanges(iChangeStart, qMax(iOldLen, getLength())-1);
}

void QAnsiLine::removeText(int iIndex, int iLength)
{
    int iChangeStart = 0;
    int iOldLen = getLength();

    if (iIndex <= -1) {  // clear
        reset();
    } else {
        if (iIndex >= getLength()) {
            //Q_ASSERT(iIndex < getLength());
        } else {
            if (isPartial(iIndex)) {  // in middle of a character
                iChangeStart = iIndex - 1;  // insert before the whole character
            } else {
                iChangeStart = iIndex;
            }
            // judge iLength
            if (iLength <= -1) {
                // content
                m_strContent.remove(m_arrIndex[iChangeStart], getTextLength()-m_arrIndex[iChangeStart]);
                // color
                m_arrColors.remove(iChangeStart, getLength()-iChangeStart);
                // effect
                m_arrEffects.remove(iChangeStart, getLength()-iChangeStart);
            } else {
                if (iChangeStart + iLength >= getLength()) {  // delete all others
                    // content
                    m_strContent.remove(m_arrIndex[iChangeStart], iLength);
                    // color
                    m_arrColors.remove(iChangeStart, iLength);
                    // effect
                    m_arrEffects.remove(iChangeStart, iLength);
                } else {
                    // get length of QString
                    int iLen = 0;
                    bool bMid = false;
                    int i;
                    for (i = 0; i < iLength; i++) {
                        if (!isPartial(iChangeStart+i)) {
                            iLen++;
                        }
                    }
                    if (iChangeStart+i < getLength()) {
                        if (isPartial(iChangeStart+i)) {
                            bMid = true;
                        }
                    }
                    // content
                    m_strContent.remove(m_arrIndex[iChangeStart], iLen);
                    if (bMid) {
                        m_strContent.insert(m_arrIndex[iChangeStart], ' ');
                    }
                    // color
                    m_arrColors.remove(iIndex, iLength);
                    // effect
                    m_arrEffects.remove(iIndex, iLength);
                }
            }
        }
    }

    updateIndex();
    Q_ASSERT(m_arrIndex.size() == m_arrColors.size());
    Q_ASSERT(m_arrIndex.size() == m_arrEffects.size());
    setChanges(iChangeStart, qMax(iOldLen, getLength())-1);
}

void QAnsiLine::replaceText(const QString &strText, int iIndex, int iLength)
{
    if (iLength <= -1) {
        removeText(iIndex, QAnsiLine(strText).getLength());
        insertText(strText, iIndex);
    } else {
        removeText(iIndex, iLength);
        insertText(strText, iIndex);
    }
    Q_ASSERT(m_arrIndex.size() == m_arrColors.size());
    Q_ASSERT(m_arrIndex.size() == m_arrEffects.size());
}

QString QAnsiLine::getText(int iIndex, int iLength) const
{
    if (m_strContent.isEmpty()) {
        return QString();
    }

    int iRealIndex = 0;
    int iRealLength = 0;
    if (iLength == 0
        || !this->calcRange(iIndex, iLength, &iRealIndex, &iRealLength)) {
        return QString();
    }

    int iCharIndex = m_arrIndex[iRealIndex];
    int iByteEnd = iRealIndex+iRealLength<getLength() ? iIndex+iLength-1 : getLength()-1;
    int iCharEnd = m_arrIndex[iByteEnd]!=-1 ? m_arrIndex[iByteEnd] : m_arrIndex[iByteEnd-1];
    int iCharLength = iCharEnd - iCharIndex + 1;
    return m_strContent.mid(iCharIndex, iCharLength);
}

QString QAnsiLine::getAnsiText(int iIndex, int iLength, const QString &strEscape) const
{
    if (m_strContent.isEmpty()) {
        return QByteArray();
    }

    int iRealIndex = 0;
    int iRealLength = 0;
    if (iLength == 0
        || !this->calcRange(iIndex, iLength, &iRealIndex, &iRealLength)) {
        return QByteArray();
    }

    char chColor = DEFAULT_COLOR;
    char chAttribute = DEFAULT_EFFECT;
    QString strResult = QString();

    int iMark = 0;
    int iEndIndex = iRealIndex + iRealLength;
    for (int i = iRealIndex; i < iEndIndex; i++) {
        iMark = i;
        chColor = m_arrColors[i];
        chAttribute = m_arrEffects[i];
        // get string of same color and effect
        while (chColor == m_arrColors[i] &&
            chAttribute == m_arrEffects[i]) {
            i++;
            if (i == iEndIndex) {
                break;
            }
        }
        QString strAttribute = getAttributeString(iMark, strEscape);
        // append the text
        strResult += strAttribute;
        strResult += getText(iMark, i - iMark);
        // skip the 2nd byte
        if (i != iEndIndex && m_arrIndex[i] == -1) {
            i++;
        }
        // we have i++ in inner loop, so restore it here.
        i--;
    }

    for (int i = iRealIndex; i < iEndIndex; i++) {
        if (m_arrColors[i] != DEFAULT_COLOR || m_arrEffects[i] != DEFAULT_EFFECT) {
            strResult += strEscape + "[m";
            break;
        }
    }
    return strResult;
}

QByteArray QAnsiLine::getEncodedAnsiText(QTextCodec *pCodec, int iIndex, int iLength, const QString &strEscape) const
{
    if (pCodec == 0 || m_strContent.isEmpty()) {
        return QByteArray();
    }

    int iRealIndex = 0;
    int iRealLength = 0;
    if (iLength == 0
        || !this->calcRange(iIndex, iLength, &iRealIndex, &iRealLength)) {
        return QByteArray();
    }

    char chColor = DEFAULT_COLOR;
    char chAttribute = DEFAULT_EFFECT;
    QByteArray arrResult = QByteArray();

    int iMark = 0;
    int iEndIndex = iRealIndex + iRealLength;
    for (int i = iRealIndex; i < iEndIndex; i++) {
        iMark = i;
        chColor = m_arrColors[i];
        chAttribute = m_arrEffects[i];
        // get string of same color and effect
        while (chColor == m_arrColors[i] &&
            chAttribute == m_arrEffects[i]) {
            i++;
            if (i == iEndIndex) {
                break;
            }
        }
        QString strAttribute = getAttributeString(iMark, strEscape);
        // get text content
        QByteArray arrCurrentContent = QByteArray();
        if (i == iEndIndex
            || (i != iEndIndex && m_arrIndex[i] != -1)) {
            QString strTemp = getText(iMark, i - iMark);
            arrCurrentContent = pCodec->fromUnicode(strTemp);
        } else {
            QString strTemp = getText(iMark, i - iMark - 1);
            QString strTemp2 = getText(i - 1, 2);
            QByteArray arrTemp = pCodec->fromUnicode(strTemp);
            QByteArray arrTemp2 = pCodec->fromUnicode(strTemp2);
            arrCurrentContent = arrTemp;
            arrCurrentContent += arrTemp2[0];
            QString strMidAttribute = getAttributeString(i, strEscape);
            arrCurrentContent += strMidAttribute;
            arrCurrentContent += arrTemp2.mid(1, -1);
            // skip the 2nd byte
            i++;
        }
        // construct all parts
        arrResult += strAttribute;
        arrResult += arrCurrentContent;
        // we have i++ in inner loop, so restore it here.
        i--;
    }

    for (int i = iRealIndex; i < iEndIndex; i++) {
        if (m_arrColors[i] != DEFAULT_COLOR || m_arrEffects[i] != DEFAULT_EFFECT) {
            arrResult += strEscape + "[m";
            break;
        }
    }
    return arrResult;
}

void QAnsiLine::insertAnsiText(const QAnsiLine &strAnsiText, int iIndex)
{
    QString strText = strAnsiText.getText();
    if (strText.isEmpty()) {
        return;
    }

    int iChangeStart = 0;
    int iOldLen = getLength();

    if (iIndex <= -1) {  // append
        iChangeStart = getLength();
        // content
        m_strContent.append(strText);
        // color
        m_arrColors.append(strAnsiText.m_arrColors);
        // effect
        m_arrEffects.append(strAnsiText.m_arrEffects);
    } else {  // insert
        if (iIndex >= getLength()) {
            QByteArray temp;
            iChangeStart = getLength();
            // content
            m_strContent += QString(iIndex-getLength(), ' ');
            m_strContent.append(strText);
            // color
            temp.fill(m_chCurrentColor, iIndex-getLength());
            m_arrColors.append(temp);
            m_arrColors.append(strAnsiText.m_arrColors);
            // effect
            temp.fill(m_chCurrentEffect, iIndex-getLength());
            m_arrEffects.append(temp);
            m_arrEffects.append(strAnsiText.m_arrEffects);
        } else {
            if (iIndex == 0) {
                iChangeStart = 0;
            } else {  // > 0
                if (isPartial(iIndex)) {  // in middle of a character
                    iChangeStart = iIndex - 1;  // insert before the whole character
                } else {
                    iChangeStart = iIndex;
                }
            }
            // content
            m_strContent.insert(m_arrIndex[iChangeStart], strText);
            // color
            m_arrColors.insert(iChangeStart, strAnsiText.m_arrColors);
            // effect
            m_arrEffects.insert(iChangeStart, strAnsiText.m_arrEffects);
        }
    }

    updateIndex();
    Q_ASSERT(m_arrIndex.size() == m_arrColors.size());
    Q_ASSERT(m_arrIndex.size() == m_arrEffects.size());
    setChanges(iChangeStart, qMax(iOldLen, getLength())-1);
}

void QAnsiLine::removeAnsiText(int iIndex, int iLength)
{
    removeText(iIndex, iLength);
}

void QAnsiLine::replaceAnsiText(const QAnsiLine &strAnsiText, int iIndex, int iLength)
{
    QString strText = strAnsiText.getText();

    if (iLength <= -1) {
        removeAnsiText(iIndex, strAnsiText.getLength());
        insertAnsiText(strAnsiText, iIndex);
    } else {
        removeAnsiText(iIndex, iLength);
        insertAnsiText(strAnsiText, iIndex);
    }
    Q_ASSERT(m_arrIndex.size() == m_arrColors.size());
    Q_ASSERT(m_arrIndex.size() == m_arrEffects.size());
}

bool QAnsiLine::getChanges(int *piStart, int *piEnd) const
{
    *piStart = m_iChangeStart;
    *piEnd = m_iChangeEnd;
    return !(m_iChangeStart==-1 && m_iChangeEnd==-1);
}

void QAnsiLine::setChanges(int iStart, int iEnd)
{
    if (iStart < -1 || iEnd < -1) {
        return;
    }
    if (iStart == -1 && iEnd == -1) {
        m_iChangeStart = 0;
        m_iChangeEnd = getLength()-1;
        return;
    }
    // mark change range
    if (m_iChangeStart == -1 && m_iChangeEnd == -1) {
        m_iChangeStart = iStart;
        m_iChangeEnd = iEnd;
    } else {
        m_iChangeStart = qMin(iStart, m_iChangeStart);
        m_iChangeEnd = qMin(qMax(iEnd, m_iChangeEnd), getLength()-1);
    }
    // update blink status
    m_bHasBlink = false;
    char chAttribute = 0;
    for (int i = 0; i < getLength(); i++) {
        chAttribute = getEffect(i);
        if (chAttribute & MASK_BLINK) {
            m_bHasBlink = true;
            break;
        }
    }
}

void QAnsiLine::clearChanges()
{
    m_iChangeStart = -1;
    m_iChangeEnd = -1;
}

bool QAnsiLine::hasBlink() const
{
    return m_bHasBlink;
}

bool QAnsiLine::isPartial(int iIndex) const
{
    if (iIndex < 0 || iIndex >= getLength()) {
        return false;
    }
    return m_arrIndex[iIndex] == (char)-1;
}

int QAnsiLine::getCharSize(int iIndex) const
{
    if (isPartial(iIndex)) {
        return 2;
    }
    if ((iIndex+1)<getLength() && isPartial(iIndex+1)) {
        return 2;
    }
    return 1;
}

int QAnsiLine::getByteIndex(int iCharIndex) const
{
    return m_arrIndex.indexOf(iCharIndex);
}

int QAnsiLine::calcCharWidth(QChar ch)
{
	/*
	 * It seems that mk_wcwidth() works not so well.
	 * We prefer to use mk_wcwidth_cjk() which supports CJK legacy encodings.
	 */
	return ::mk_wcwidth_cjk(ch.unicode());
}

void QAnsiLine::reset()
{
    m_strContent = QString();
    m_arrIndex = QByteArray();
    m_arrColors = QByteArray();
    m_arrEffects = QByteArray();
    m_bHasBlink = false;
    m_chCurrentColor = DEFAULT_COLOR;
    m_chCurrentEffect = DEFAULT_EFFECT;
    m_iChangeStart = 0;
    m_iChangeEnd = 0;
}

void QAnsiLine::updateIndex()
{
    m_arrIndex.clear();
    for (int i = 0; i < getTextLength(); i++) {
        int iLen = QAnsiLine::calcCharWidth(m_strContent.at(i));
        if (iLen <= 0) {
            // non-printable
            continue;
        }
        m_arrIndex.append(i);
        if (iLen > 1) {
            // omit 2nd character
            m_arrIndex.append(-1);
        }
    }
}

bool QAnsiLine::calcRange(int iIndex, int iLength, int *piIndex, int *piLength) const
{
    if (iIndex >= getLength()) {
        return false;
    }
    if (piIndex == 0 && piLength == 0) {
        return true;
    }
    // init
    int iIndexRet = 0;
    int iLengthRet = 0;
    // calculate
    if (iIndex <= -1) {
        iIndexRet = 0;
        iLengthRet = getLength();
    } else {
        // index
        iIndexRet = iIndex;
        if (m_arrIndex[iIndex] == -1) {
            iIndexRet = iIndex - 1;
        }
        // length
        if ((iLength <= -1)
            || (iIndex+iLength >= getLength())) {
            iLengthRet = getLength() - iIndexRet;
        } else {
            if (m_arrIndex[iIndex+iLength] == -1) {
                iLengthRet = iLength + 1;
            } else {
                iLengthRet = iLength;
            }
        }
    }
    // return
    if (piIndex != 0) {
        *piIndex = iIndexRet;
    }
    if (piLength != 0) {
        *piLength = iLengthRet;
    }
    return true;
}

QString QAnsiLine::getAttributeString(int iIndex, const QString &strEscape) const
{
    if (iIndex < 0 || iIndex >= getLength()) {
        return QString();
    }

    char chColor = m_arrColors[iIndex];
    char chEffect = m_arrEffects[iIndex];
    char chPrevColor = DEFAULT_COLOR;
    char chPrevEffect = DEFAULT_EFFECT;
    int iFG = GETFG(chColor);
    int iBG = GETBG(chColor);

    /* Whether we should add bg, fg, effect flags */
    bool bHasFG = false;
    bool bHasBG = false;
    bool bHasEffect = false;
    if (iIndex != 0) {
        chPrevColor = m_arrColors[iIndex-1];
        chPrevEffect = m_arrEffects[iIndex-1];
    }
    if (iFG != GETFG(chPrevColor)) {
        bHasFG = true;
    }
    if (iBG != GETBG(chPrevColor)) {
        bHasBG = true;
    }
    if (chEffect != chPrevEffect) {
        bHasEffect = true;
        // prefer *[0m than default color
        if (chEffect == DEFAULT_EFFECT) {
            bHasFG = (iFG != GETFG(DEFAULT_COLOR));
            bHasBG = (iBG != GETBG(DEFAULT_COLOR));
        }
    }

    /* Construct the attribute string */
    if (!bHasEffect && !bHasFG && !bHasBG) {
        return QString();
    }
    QString strAttribute = strEscape + "[";
    if (bHasEffect) {
        if (chEffect == DEFAULT_EFFECT) {
            strAttribute += "0;";
        } else {
            if ((chEffect & MASK_BOLD)
                && !(chPrevEffect & MASK_BOLD)) {
                strAttribute += "1;";
            }
            if ((chEffect & MASK_FAINT)
                && !(chPrevEffect & MASK_FAINT)) {
                strAttribute += "2;";
            }
            if ((chEffect & MASK_ITALIC)
                && !(chPrevEffect & MASK_ITALIC)) {
                strAttribute += "3;";
            }
            if ((chEffect & MASK_UNDERLINE)
                && !(chPrevEffect & MASK_UNDERLINE)) {
                strAttribute += "4;";
            }
            if ((chEffect & MASK_BLINK)
                && !(chPrevEffect & MASK_BLINK)) {
                strAttribute += "5;";
            }
            if ((chEffect & MASK_RAPIDBLINK)
                && !(chPrevEffect & MASK_RAPIDBLINK)) {
                strAttribute += "6;";
            }
            if ((chEffect & MASK_REVERSE)
                && !(chPrevEffect & MASK_REVERSE)) {
                strAttribute += "7;";
            }
            if ((chEffect & MASK_INVISIBLE)
                && !(chPrevEffect & MASK_INVISIBLE)) {
                strAttribute += "8;";
            }
        }
    }
    if (bHasFG) {
        strAttribute += QString::number(iFG+30) + ";";
    }
    if (bHasBG) {
        strAttribute += QString::number(iBG+40) + ";";
    }
    strAttribute.remove(strAttribute.length() - 1, 1);
    strAttribute += "m";

    return strAttribute;
}


}  // namespace QAnsiEditor
